<?php
	
	namespace Aspic;
	
	use Aspic as A;
	use Aspic\Security\Security as S;
	
	/*
	
		$json = new A\JsonNode();
		$json['a'] = array(1, 2, 3);
		
		$json['b']['c']['d'] = 2;
		$json['x']->set('alert(2)')->setType($json::TYPE_JS);
		
		print_r($json);
	
	*/
	
	class JsonNode implements \ArrayAccess {
		const TYPE_SCALAR = 'scalar';
		const TYPE_OBJECT = 'object';
		const TYPE_ARRAY = 'array';
		const TYPE_JS = 'javascript';
		
		const JSON_AFFECT = ': ';
		const JSON_SEPARATOR = ', ';
		const JSON_BR = "\n";
		
		protected static $_prettyPrint;
		protected static $_jsonIndent = '     ';
		
		protected $_type;
		protected $_value;
		
		public function __construct($value = array(), $type = null) {
			$this->_type = static::TYPE_ARRAY;
			$this->set($value, $type);
		}
		
		public static function setIndent($indent) {
			static::$_jsonIndent = $indent;
		}
		
		/**
		* Return true if a php array will be treated as a json array or false if it will be treated as an object
		*/
		protected static function _isTreatedAsArray(array $arr) {
			return (isset($arr[0]) OR !count($arr));
		}
		
		public function getType() {
			return $this->_type;
		}
		
		public function setType($type) {
			$this->_type = $type;
			
			return $this;
		}
		
		/**
		* Create Json nodes(s) from data
		*/
		protected static function _encodeData($data) {
			if($data instanceof \stdclass) {
				$data = (array)$data;
			}
			
			if(is_array($data)) {
				$encoded = array();
			
				foreach($data as $key => $value) {
					$obj = new self($value);
					$encoded[$key] = $obj;
				}
				
				
			}
			// elseif($data instanceof self) {
				// $encoded = $data->get();
			// }
			else {
				$encoded = $data;
			}
			
			return $encoded;
		}
		
		public function set($value, $userType = null) {
			if($value instanceof self) {
				$this->_value = $value->get();
				$this->setType($value->getType());
			}
			else {
				// var_dump($value);
				if(!$userType) {
					if($value instanceof \stdclass) {
						$value = (array)$value;
					}
				
					if(is_array($value)) {
						$treatedAsArray = static::_isTreatedAsArray($value);
						
						if($treatedAsArray) {
								$type = static::TYPE_ARRAY;
						}
						else {
							$type = static::TYPE_OBJECT;
						}
					}
					else {
						$type = static::TYPE_SCALAR;
					}
				}
				else {
					$type = $userType;
				}
				
				$this->setType($type);
				
				$this->_value = static::_encodeData($value);
			}
			
			return $this;
		}
		
		public function get() {
			return $this->_value;
		}
		
		public function offsetSet($offset, $value) {
			$el = $this->offsetGet($offset);
			$el->set($value);
			// var_dump($el);
			// echo 'a';
		}
		
		public function offsetExists($offset) {
			return (is_array($this->_value) AND isset($this->_value[$offset]));
		}
		
		public function offsetUnset($offset) {
			if(is_array($this->_value) AND isset($this->_value[$offset])) {
				unset($this->_value[$offset]);
			}
		}
		
		public function offsetGet($offset) {
			if(is_array($this->_value)) {
				if(isset($this->_value[$offset])) {
					$el = $this->_value[$offset];
				}
				else {
					$el = new self;
					$this->_value[$offset] = $el;
					
					if(is_numeric($offset) OR $offset == '') {
						$this->setType(static::TYPE_ARRAY);
						$this->_value[] = $el;
					}
					else {
						$this->setType(static::TYPE_OBJECT);
					}
				}
			}
			else {
				throw new Exception('Cannot get from other than array/object');
			}
			// var_dump($el);
			// echo 'b';
			return $el;
		}
		
		public function count() {
			if(is_array($this->_value)) {
				return count($this->_value);
			}
			else {
				throw new Exception('Cannot count on not array value');
			}
		}
		
		public function sizeof($offset) {
			return $this->count($offset);
		}
		
		public function __toString() {
			if($this->getType() == static::TYPE_SCALAR) {
				return $this->get();
			}
			else {
				return $this->out();
			}
		}
		
		/**
		* Return a JsonNode based on given data
		*/
		public static function getNode($data) {
			if($data instanceof \stdclass) {
				$data = (array)$data;
			}
		
			if(is_array($data)) {
				$node = new self($data);
			}
			elseif($data instanceof self) {
				$node = $data;
			}
			else {
				$node = new self;
			}
			
			return $node;
		}
		
		/**
		* Merge two node
		*/
		public static function merge(JsonNode $node1, JsonNode $node2) {
			throw new A\NotImplementedException;
		}
		
		public static function encode($data) {
			$node = static::getNode($data);
			
			return $node->out();
		}
		
		/**
		* @param string $prettyPrintTab Tabulation used to pretty print
		*/
		public function out($prettyPrint = false) {
			static::$_prettyPrint = $prettyPrint;
			
			return static::_outEl($this, 0);
		}
		
		protected static function _outEl($el, $tabLevel) {
			if($el->getType() == static::TYPE_ARRAY) {
				$out = static::_outArray($el, $tabLevel);
			}
			elseif($el->getType() == static::TYPE_OBJECT) {
				$out = static::_outObject($el, $tabLevel);
			}
			elseif($el->getType() == static::TYPE_JS) {
				$out = static::_outJs($el);
			}
			elseif($el->getType() == static::TYPE_SCALAR) {
				$out = static::_outScalar($el);
			}
			else {
				throw new Exception('Element type : "'.$el->getType().'" unknowned');
			}
			
			
			return $out;
		}
		
		protected static function _outObject($src, $tabLevel) {
			$out = '{'.static::_outBr();
			
			$tabLevel++;
			
			if(count($src->_value)) {
				foreach($src->_value as $key => $el) {
						$out .= 
							static::_outTabs($tabLevel).
							'"'.S::escapeDoubleQuotes($key).'"'.
							static::JSON_AFFECT.
							static::_outEl($el, $tabLevel).
							static::_outSeparator();
				
				}
				
				$out = A\UString::removeLast($out, static::_outSeparator());
			}
			
			$out .= static::_outBr().static::_outTabs($tabLevel - 1).'}';
			
			return $out;
		}
		
		protected static function _outArray($src, $tabLevel) {
			$out = '['.static::_outBr();
			
			$tabLevel++;
			
			if(count($src->_value)) {
				foreach($src->_value as $key => $el) {
					$out .= 
						static::_outTabs($tabLevel).
						static::_outEl($el, $tabLevel).
						static::_outSeparator();
				}
				
				$out = A\UString::removeLast($out, static::_outSeparator());
			}
			
			$out .= static::_outBr().static::_outTabs($tabLevel - 1).']';
			
			return $out;
		}
		
		protected static function _outScalar($el) {
			if(is_numeric($el->get())) {
				$out = $el->get();
			}
			elseif(is_bool($el->get())) {
				$out = A\UString::boolToString($el->get());
			}
			else {
				$out = '"'.S::escapeDoubleQuotes($el->get()).'"';
			}
			
			return $out;
		}
		
		protected static function _outJs($el) {
			$out = $el->get();
			
			return $out;
		}
		
		protected static function _outTabs($tabLevel) {
			if(static::$_prettyPrint) {
				return str_repeat(static::$_jsonIndent, $tabLevel);
			}
			else {
				return '';
			}
		}
		
		protected static function _outSeparator() {
			if(static::$_prettyPrint) {
				return static::JSON_SEPARATOR.static::JSON_BR;
			}
			else {
				return static::JSON_SEPARATOR;
			}
		}
		
		protected static function _outBr() {
			if(static::$_prettyPrint) {
				return static::JSON_BR;
			}
			else {
				return '';
			}
		}
		
	}
	
?>